import * as THREE from "three";

// 引入轨道控制器控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 引入动画库
import gsap from "gsap";
// 引入数据图形用户界面库
import * as dat from "dat.gui";
// 引入rgbeLoad
// DataTextureLoader
// 用于加载二进制文件格式的(rgbe, hdr, ...)的抽象类。 内部使用FileLoader来加载文件， 和创建一个新的 DataTexture.
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"


console.log(THREE);

//three.js 基本内容

// 1.创建场景  场景能够让你在什么地方、摆放什么东西来交给three.js来渲染，这是你放置物体、灯光和摄像机的地方。
const scene = new THREE.Scene();

// 2.创建相机 

//  透视相机（PerspectiveCamera）这一摄像机使用perspective projection（透视投影）来进行投影。
// 这一投影模式被用来模拟人眼所看到的景象，它是3D场景的渲染中使用得最普遍的投影模式。
// PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
// fov — 摄像机视锥体垂直视野角度   // aspect — 摄像机视锥体长宽比  // near — 摄像机视锥体近端面  // far — 摄像机视锥体远端面
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 20);

//2.1设置相机的位置 position表示对象局部位置的Vector3。默认值为(0, 0, 0)。

// Vector3( x : Float, y : Float, z : Float )   该类表示的是一个三维向量（3D vector）。'
//  一个三维向量表示的是一个有顺序的、三个为一组的数字组合（标记为x、y和z）， 可被用来表示很多事物:
//  一个位于三维空间中的点。
// 一个在三维空间中的方向与长度的定义。在three.js中，长度总是从(0, 0, 0)到(x, y, z)的 Euclidean distance（欧几里德距离，即直线距离）， 
// 方向也是从(0, 0, 0)到(x, y, z)的方向。
// .set ( x : Float, y : Float, z : Float ) : this
// 设置该向量的x、y 和 z 分量。

camera.position.set(0, 0, 20);
scene.add(camera); //添加相机到场景中


function createPoints(src, size = 10) {
// 3.创建物体 其他种类的几何体  
// 初始化球体与材质

// 是面片、线或点几何体的有效表述。包括顶点位置，面片索引、法相量、颜色值、UV 坐标和自定义缓存属性值。
// 使用 BufferGeometry 可以有效减少向 GPU 传输上述数据所需的开销。
// .setAttribute ( name : String, attribute : BufferAttribute ) : this

const particlesGeometry = new THREE.BufferGeometry();
    const count = 50000;

//生成顶点数据 与 颜色
    const positions = new Float32Array(count * 3);
    const colors = new Float32Array(count * 3);

for (let i = 0; i < count * 3; i++) {
        positions[i] =( Math.random() -0.5 ) * 200;
        colors[i] = Math.floor(Math.random() * 510) - 255;
}

// 为当前几何体设置一个 attribute 属性。在类的内部，有一个存储 .attributes 的 hashmap， 
// 通过该 hashmap，遍历 attributes 的速度会更快。而使用该方法，可以向 hashmap 内部增加 attribute。 
// 所以，你需要使用该方法来添加 attributes。
// itemSize = 3 因为每个顶点都是一个三元组。
    particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    particlesGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));


// 创建点材质
// Points使用的默认材质。
// parameters - (可选)用于定义材质外观的对象，具有一个或多个属性。 材质的任何属性都可以从此处传入(包括从Material继承的任何属性)。
// 属性color例外，其可以作为十六进制字符串传递，默认情况下为 0xffffff（白色），内部调用Color.set(color)。
// .size : Number
// 设置点的大小。默认值为1.0。

const pointsMaterial = new THREE.PointsMaterial({
        size: size
 });
// pointsMaterial.color.set(0xfff000);
// .sizeAttenuation : Boolean
// 指定点的大小是否因相机深度而衰减。（仅限透视摄像头。）默认为true。
pointsMaterial.sizeAttenuation = false;

// 设置点材质纹理
// 导入纹理
const textureloader = new THREE.TextureLoader();

const sprite = textureloader.load(src);
pointsMaterial.map = sprite;
pointsMaterial.alphaMap = sprite;

// 将混合模式改为正常混合
pointsMaterial.transparent = true;
// .depthWrite : Boolean
// 渲染此材质是否对深度缓冲区有任何影响。默认为true。
// 在绘制2D叠加时，将多个事物分层在一起而不创建z-index时，禁用深度写入会很有用。
pointsMaterial.depthWrite = false;

// .blending : Blending
// 在使用此材质显示对象时要使用何种混合。
// 必须将其设置为CustomBlending才能使用自定义blendSrc, blendDst 
// 或者 [page:Constant blendEquation]。 混合模式所有可能的取值请参阅constants。默认值为NormalBlending。
pointsMaterial.blending = THREE.AdditiveBlending;


// .vertexColors : Boolean
// 是否使用顶点着色。默认值为false。 此引擎支持RGB或者RGBA两种顶点颜色，
// 取决于缓冲 attribute 使用的是三分量（RGB）还是四分量（RGBA）
pointsMaterial.vertexColors = true;

// 一个用于显示点的类。 由WebGLRenderer渲染的点使用 gl.POINTS。
// Points( geometry : BufferGeometry, material : Material )
// geometry —— （可选）是一个BufferGeometry的实例，默认值是一个新的BufferGeometry。
// material —— （可选） 是一个对象，默认值是一个PointsMaterial。
    const points = new THREE.Points(particlesGeometry, pointsMaterial);

    scene.add(points);
    return points;

}

const points  = createPoints("./textures/particles/snowflake2.png",40);
const points2  = createPoints("./textures/particles/snowflake2.png",40);
const points3  = createPoints("./textures/particles/1.png",25);


// 4.渲染
// 初始化渲染器
// WebGLRenderer WebGL Render 用WebGL渲染出你精心制作的场景。
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// .domElement : DOMElement   一个canvas，渲染器在其上绘制输出。
// 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
document.body.appendChild(renderer.domElement); //将绘制canvas添加到页面里
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
renderer.physicallyCorrectLights = true; // 新版本不用设置 物理正确光源 decay也会生效。 老版本开阴影要设置


//创建轨道控制器
// OrbitControls( object : Camera, domElement : HTMLDOMElement )
// object: （必须）将要被控制的相机。该相机不允许是其他任何对象的子级，除非该对象是场景自身。
// domElement: 用于事件监听的HTML元素。
const controls = new OrbitControls(camera, renderer.domElement);
// 将其设置为true以启用阻尼（惯性），这将给控制器带来重量感。默认值为false。
// 请注意，如果该值被启用，你将必须在你的动画循环里调用.update()。
controls.enableDamping = true;

// 创建坐标轴辅助器
// AxesHelper  用于简单模拟3个坐标轴的对象.
// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
// AxesHelper( size : Number )  size -- (可选的) 表示代表轴的线段长度. 默认为 1.
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 设置时钟
// Clock该对象用于跟踪时间。如果performance.now可用，则 Clock 对象通过该方法实现，否则回落到使用略欠精准的Date.now来实现。
// .oldTime : Float 存储时钟最后一次调用 start, .getElapsedTime() 或 .getDelta() 方法的时间。默认值是 0。
const clock = new THREE.Clock();


function render() {
    let time = clock.getElapsedTime();
    points.position.y = time * -2 ;

    points2.position.y = time * -2.5 ;
    points2.position.x = time * -2 ;

    points3.position.y = time * -3 ;

    controls.update();
    renderer.render(scene, camera);
    // requestAnimationFrame 是一个用于优化浏览器动画效果的 API。它可以让浏览器在下一次重绘前执行指定的回调函数，
    // 从而可以更加流畅地执行动画效果，避免了使用 setTimeout 或 setInterval 可能引起的性能问题。
    requestAnimationFrame(render);
}

render()

// 窗口变化时，更新渲染画面
window.addEventListener("resize", () => {

    // 更新摄像机视锥体长宽比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
    camera.updateProjectionMatrix();

    // 更新渲染器
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 设置渲染器像素比 通常用于避免HiDPI设备上绘图模糊
    renderer.setPixelRatio(window.devicePixelRatio);

})


